<template>
  <div class="file-upload">
    <div class="file-upload-container" @dragover.prevent :class="dropActive ? 'drop-active' : ''"
      @click.prevent="triggerUpload" @drop.prevent="triggerDropUpload" @dragenter="triggerDragEnter"
      @dragleave="triggerDragleave" v-bind="$attrs">
      <slot v-if="fileStatus === 'loading'" name="loading">
        <div class="d-flex flex-column">
          <div class="progress">
            <div class="progress-bar" :style="{ width: fileProgress }">
              {{ fileProgress }}
            </div>
          </div>
          <div>正在上传...</div>
        </div>
      </slot>
      <slot v-else-if="fileStatus === 'success'" name="uploaded" :uploadedData="uploadedData">
        <button class="btn btn-primary">上传成功</button>
      </slot>
      <slot v-else name="default">
        <button class="btn btn-primary">点击上传</button>
      </slot>
    </div>
    <input type="file" class="file-input d-none" ref="fileInput" @change.prevent="handleFileChange" />
  </div>
</template>

<script lang="ts">
/**
 * @component
 * @assetTitle 上传文件组件
 * @assetDescription
 * @assetImportName Uploader
 * @assetTag 通用组件
 */
import { defineComponent, ref, PropType, watch } from "vue";
import axios from "axios";
// 多个上传状态
type UploadStatus = "ready" | "loading" | "success" | "error";
// 定义一个测试的func 测试当前上传的文件是否满足条件
type CheckFunction = (file: File) => boolean;
export default defineComponent({
  props: {
    action: {
      type: String,
      required: true
    },
    beforeUpload: {
      type: Function as PropType<CheckFunction>
    },
    // 如果传入这个属性 说明已经上传过图片了
    uploaded: {
      type: Object
    }
  },
  // 自定义样式添加到uploader的元素中
  inheritAttrs: false,
  //   自定义触发事件 上传成功 上传失败
  emits: ["file-uploaded", "file-uploaded-error"],
  setup(props, context) {
    const uploadedData = ref(props.uploaded);
    // 此时打印uploaded会发现是undefined setup中只执行了一次 所以需要监听uploaded属性
    // 这里还有一个要点就是被监视的对象要么是个响应式对象，要么写成getter的形式()=>{}
    watch(
      () => props.uploaded,
      newValue => {
        if (newValue) {
          fileStatus.value = "success";
          uploadedData.value = newValue;
        }
      }
    );
    //   fileInput是一个联合泛型 在setup中还不是dom节点类型，dom真正挂载之后才是 所以初始化为null
    const fileInput = ref<null | HTMLInputElement>(null);
    //  上传状态
    const fileStatus = ref<UploadStatus>(props.uploaded ? "success" : "ready");
    //   ???如何获取页面中的元素 -> 元素绑定ref，setup中暴露同名对象，泛型为ref<null | HTMLXXXElement>(null);
    // 上传进度数据
    const fileProgress = ref("");
    const triggerUpload = () => {
      //   console.log(fileInput.value); //为真实的dom对象
      if (fileInput.value) {
        // console.log(fileInput.value);
        fileInput.value.click(); // 模拟点击上传按钮
      }
    };
    const dropActive = ref(false);
    const triggerDropUpload = (e: DragEvent) => {
      var fileList = e.dataTransfer && e.dataTransfer.files; //获取拖拽中的文件对象
      const files = Array.from(fileList || []);
      if (files.length) {
        // console.log(files[0]);
        //  在这里检查上传的文件是否满足用户的一些自定义需求
        if (props.beforeUpload) {
          const result = props.beforeUpload(files[0]); // 调用传入的函数获得结果
          if (!result) {
            return;
          }
        }
        fileStatus.value = "loading";
        uploadFile(files);
      }
    };
    const triggerDragEnter = () => {
      dropActive.value = true;
    };
    const triggerDragleave = () => {
      dropActive.value = false;
    };

    // 当选择了文件的时候触发的事件 ——> 原生是change事件
    const handleFileChange = (e: Event) => {
      // 这种写法能实现 但是没用到ts特性 比较简陋
      //   if (e.target) {
      //     fileStatus.value = "loading";
      //   }
      const currentTarget = e.target as HTMLInputElement; // 为了拿到files属性
      if (currentTarget.files) {
        const files = Array.from(currentTarget.files);
        //   在这里检查上传的文件是否满足用户的一些自定义需求
        if (props.beforeUpload) {
          const result = props.beforeUpload(files[0]); // 调用传入的函数获得结果
          if (!result) {
            return;
          }
        }
        fileStatus.value = "loading";
        uploadFile(files);
      }
    };
    // 上传文件
    const uploadFile = (files: File[]) => {
      if (files && files.length) {
        const formData = new FormData();
        formData.append("file", files[0]);
        axios
          .post(props.action, formData, {
            headers: {
              "Content-Type": "multipart/form-data"
            },
            // 配置项 获取上传进度
            // progress事件
            onUploadProgress: function (progressEvent) {
              // 这里的配置项不能写成箭头函数的形式
              fileProgress.value =
                (((progressEvent.loaded / progressEvent.total) * 100) | 0) +
                "%";
            }
          })
          .then((res: any) => {
            console.log(res.data);
            uploadedData.value = res.data;
            fileStatus.value = "success";
            context.emit("file-uploaded", res.data);
          })
          .catch(error => {
            console.error(error);
            fileStatus.value = "error";
            context.emit("file-uploaded-error", { error });
          })
          .finally(() => {
            // promise状态已完成改变 此时还原fileStatus
            if (fileInput.value) {
              fileInput.value.value = ""; // 把input选择值置为空
            }
          });
      }
    };
    return {
      fileInput,
      triggerUpload,
      fileStatus,
      handleFileChange,
      uploadedData,
      fileProgress,
      triggerDropUpload,
      triggerDragEnter,
      triggerDragleave,
      dropActive
    };
  }
});
</script>

<style lang="less" scoped>
.progress {
  width: 100px;
  background-color: @Bg_blue1;
  height: 20px;
}
</style>
